package org.codefilarete.stalactite.engine.configurer.dslresolver;

import java.util.Map;

import org.codefilarete.reflection.AccessorDefinition;
import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.CompositeKeyMapping;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.KeyMapping;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.SingleKeyMapping;
import org.codefilarete.stalactite.dsl.idpolicy.GeneratedKeysPolicy;
import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfiguration;
import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMapping;
import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingBuilder;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
import org.codefilarete.tool.VisibleForTesting;

import static org.codefilarete.tool.Nullable.nullable;

public class PrimaryKeyResolver<C, I> {
	
	/**
	 * Creates a primary key on a table that owns the identification
	 *
	 * @param keyLinkage information that allow to create primary key
	 * @return the created {@link PrimaryKey}
	 */
	@VisibleForTesting
	public <T extends Table<T>> PrimaryKey<T, I> addIdentifyingPrimarykey(KeyMapping<C, I> keyLinkage,
	                                                                      T pkTable,
	                                                                      ColumnBinderRegistry columnBinderRegistry,
	                                                                      ColumnNamingStrategy columnNamingStrategy,
	                                                                      UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy) {
		AccessorDefinition identifierDefinition = AccessorDefinition.giveDefinition(keyLinkage.getAccessor());
		if (keyLinkage instanceof CompositeKeyMapping) {
			CompositeKeyMappingConfiguration<I> configuration = ((CompositeKeyMapping<C, I>) keyLinkage).getCompositeKeyMappingBuilder().getConfiguration();
			// Note that we won't care about readonly column returned by build(..) since we're on a primary key case, then
			// some readonly columns would be nonsense
			EmbeddableMappingBuilder<I, T> compositeKeyBuilder = new EmbeddableMappingBuilder<>(configuration, pkTable, columnBinderRegistry, columnNamingStrategy, uniqueConstraintNamingStrategy);
			EmbeddableMapping<I, T> build = compositeKeyBuilder.build();
			Map<ReadWritePropertyAccessPoint<I, Object>, Column<T, Object>> compositeKeyMapping = build.getMapping();
			compositeKeyMapping.values().forEach(Column::primaryKey);
		} else {
			SingleKeyMapping<C, I> singleKeyLinkage = (SingleKeyMapping<C, I>) keyLinkage;
			String columnName = determineColumnName(singleKeyLinkage, columnNamingStrategy);
			// nullability = false may not be necessary because of primary key, let for principle
			Column<?, I> primaryKey = pkTable.addColumn(columnName, identifierDefinition.getMemberType(), singleKeyLinkage.getColumnOptions().getColumnSize(), false);
			primaryKey.primaryKey();
			if (singleKeyLinkage.getIdentifierPolicy() instanceof GeneratedKeysPolicy) {
				primaryKey.autoGenerated();
			}
		}
		return pkTable.getPrimaryKey();
	}

	private <O> String determineColumnName(SingleKeyMapping<C, O> keyLinkage, ColumnNamingStrategy columnNamingStrategy) {
		return nullable(keyLinkage.getColumnOptions().getColumnName())
				.elseSet(keyLinkage::getFieldName)
				.elseSet(() -> columnNamingStrategy.giveName(AccessorDefinition.giveDefinition(keyLinkage.getAccessor())))
				.get();
	}
}
